# ==============================================================================
# 模块导入区：导入所有依赖的库和模块
# ==============================================================================
import sys
import random
from hashlib import sha256  # 用于用户密码哈希处理
from pathlib import Path  # 用于路径处理（跨系统兼容）
from datetime import datetime  # 用于时间戳生成

from flask import Flask, render_template, request, jsonify  # Flask核心功能
from flask_sqlalchemy import SQLAlchemy  # 数据库ORM
import click  # 用于创建命令行指令

# ==============================================================================
# 1. Flask应用初始化：创建应用实例，配置基础参数
# ==============================================================================
# 创建Flask应用实例，__name__表示当前模块名，Flask会据此查找模板/静态文件
app = Flask(__name__)

# ==============================================================================
# 2. 数据库配置与初始化：配置数据库连接、创建数据库实例
# ==============================================================================
# 定义数据库文件夹路径：项目根目录下的 instance 文件夹，Flask官方推荐目录
# app.instance_path：自动指向项目根目录下的 instance 文件夹（无需手动创建路径）
DB_FOLDER = Path(app.instance_path)
DB_FOLDER.mkdir(exist_ok=True)  # exist_ok=True：文件夹已存在时不报错，避免重复创建
# 拼接数据库地址
DB_PATH = DB_FOLDER / 'data.db'

# 跨平台SQLite路径前缀：Windows用3个斜杠，其他系统用4个
SQLITE_PREFIX = 'sqlite:///' if sys.platform.startswith('win') else 'sqlite:////'

# 配置数据库连接URI（SQLAlchemy需要的核心配置）
app.config['SQLALCHEMY_DATABASE_URI'] = SQLITE_PREFIX + str(DB_PATH)
# 关闭SQLAlchemy的修改跟踪功能（减少性能开销，官方推荐关闭）
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# ==============================================================================
# 3. 数据库模型定义：映射数据库表结构（ORM核心）
# ==============================================================================
# 初始化SQLAlchemy数据库实例（关联Flask应用，后续通过db操作数据库）
db = SQLAlchemy(app)


class User(db.Model):
    """用户模型：对应数据库中的user表"""
    id = db.Column(db.Integer, primary_key=True)  # 主键ID（自增）
    username = db.Column(db.String(30), unique=True, nullable=False)  # 用户名（唯一、非空）
    email = db.Column(db.String(50), unique=True, nullable=False)  # 邮箱（唯一、非空）
    password_hash = db.Column(db.String(128), nullable=False)  # 密码哈希（非空，存储加密后的密码）
    created_at = db.Column(db.String(20), nullable=False)  # 创建时间戳（非空，字符串格式）


class Movie(db.Model):
    """电影模型：对应数据库中的movie表"""
    # 表名默认是类名小写（可通过__tablename__手动指定）
    id = db.Column(db.Integer, primary_key=True)  # 主键ID（自增）
    title = db.Column(db.String(60), nullable=False)  # 电影标题（非空，最长60字符）
    year = db.Column(db.Integer, nullable=False)  # 上映年份（非空）
    genre = db.Column(db.String(30), nullable=False)  # 电影类型（非空，最长30字符）
    rating = db.Column(db.Float, nullable=False)  # 评分（非空，浮点型）
    poster_url = db.Column(db.String(200), nullable=False)  # 海报图片URL（非空，最长200字符）
    description = db.Column(db.Text, default='暂无介绍')  # 电影描述（文本类型，默认值）
    created_at = db.Column(db.String(20), nullable=False)  # 创建时间戳（非空，字符串格式）
    updated_at = db.Column(db.String(20), nullable=False)  # 更新时间戳（非空，字符串格式）


# ==============================================================================
# 4. 命令行指令：用于数据库初始化、测试数据生成（通过flask cli调用）
# ==============================================================================
@app.cli.command('init-db')
@click.option('--drop', is_flag=True, help='删除现有表后重建（可选参数）')
def init_db(drop):
    """初始化数据库：创建所有数据表（支持先删后建）"""
    if drop:  # 如果指定--drop参数，先删除所有已存在的表
        db.drop_all()
    db.create_all()  # 创建所有模型对应的表
    click.echo('✅ 数据库初始化成功！')  # 命令行输出成功提示


@app.cli.command('forge')
@click.option('--movies', '-m', default=10, help='生成电影数量（默认10部）')
@click.option('--users', '-u', default=3, help='生成普通用户数量（默认3个）')
def forge(movies, users):
    """批量生成测试数据：包括管理员用户、普通用户、电影数据（会清除现有数据）"""
    # 1. 清除现有数据（先删后建，避免重复）
    db.drop_all()
    db.create_all()

    # 2. 生成测试用户数据
    # 普通用户模板（按指定数量生成）
    test_users = [
        {
            "username": f"user{i}",
            "email": f"user{i}@example.com",
            "password": f"password{i}"
        } for i in range(1, users + 1)
    ]

    # 添加管理员用户（固定账号，密码哈希为示例值，实际使用需修改）
    admin_user = User(
        username="admin",
        email="admin@example.com",
        password_hash="pbkdf2:sha256:150000$XXXXXXXX$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        created_at=datetime.now().strftime('%Y-%m-%d %H:%M:%S')  # 当前时间格式化
    )
    db.session.add(admin_user)  # 将管理员用户添加到数据库会话

    # 添加普通用户（密码通过sha256简单哈希处理，实际生产环境建议用werkzeug.security）
    for user_data in test_users:
        # 密码哈希生成：拼接前缀+sha256加密后的字符串
        password_hash = f"hash_{sha256(user_data['password'].encode()).hexdigest()}"
        user = User(
            username=user_data["username"],
            email=user_data["email"],
            password_hash=password_hash,
            created_at=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        )
        db.session.add(user)  # 将普通用户添加到数据库会话

    # 3. 生成测试电影数据
    genres = ["动作", "科幻", "喜剧", "剧情", "悬疑", "爱情", "动画", "冒险"]  # 电影类型列表
    movie_titles = [
        "星际穿越", "盗梦空间", "复仇者联盟", "肖申克的救赎", "霸王别姬",
        "阿甘正传", "泰坦尼克号", "蝙蝠侠：黑暗骑士", "教父", "指环王",
        "哈利波特", "蜘蛛侠", "变形金刚", "速度与激情", "侏罗纪公园"
    ]  # 电影标题列表

    for i in range(movies):
        # 处理标题：如果生成数量超过标题列表长度，自动拼接序号避免重复
        if i >= len(movie_titles):
            title = f"{movie_titles[i % len(movie_titles)]}{i + 1}"
        else:
            title = movie_titles[i]

        # 随机生成电影属性
        genre = random.choice(genres)  # 随机选择类型
        year = random.randint(1990, 2024)  # 随机年份（1990-2024）
        rating = round(random.uniform(7.0, 9.8), 1)  # 随机评分（7.0-9.8，保留1位小数）

        # 创建电影实例
        movie = Movie(
            title=title,
            year=year,
            genre=genre,
            rating=rating,
            poster_url=f"https://via.placeholder.com/300x450?text={title}",  # 占位图URL
            description=f"{title}是一部精彩的{genre}电影，上映于{year}年，获得了{rating}的高分评价。",
            created_at=datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            updated_at=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        )
        db.session.add(movie)  # 将电影添加到数据库会话

    # 提交会话：将所有数据写入数据库
    db.session.commit()
    # 命令行输出结果提示
    click.echo(f'✅ 测试数据生成完成！')
    click.echo(f'- 生成用户：{users + 1} 个（含1个管理员 + {users}个普通用户）')
    click.echo(f'- 生成电影：{movies} 部')


# ==============================================================================
# 5. 模板全局变量：向所有模板注入公共数据（无需在每个视图函数中传递）
# ==============================================================================
@app.context_processor
def inject_vars():
    """注入全局变量到所有模板中"""
    return {'current_year': datetime.now().year}  # 注入当前年份（用于模板底部版权信息等）


# ==============================================================================
# 6. 页面路由：渲染HTML页面（面向用户的页面访问）
# ==============================================================================
@app.route('/')
@app.route('/home')
@app.route('/index')
def index():
    """首页/电影列表页：支持3个访问路径（/、/home、/index）"""
    # 查询所有电影，按ID倒序排列（最新添加的电影在前）
    movies = Movie.query.order_by(Movie.id.desc()).all()
    # 格式化电影数据为字典列表（避免直接传递ORM对象到模板，更安全）
    formatted_movies = [{
        'id': m.id, 'title': m.title, 'year': m.year,
        'genre': m.genre, 'rating': m.rating,
        'poster_url': m.poster_url, 'description': m.description
    } for m in movies]
    # 渲染电影列表模板（模板路径：templates/pages/movies.html）
    return render_template('pages/movies.html', movies=formatted_movies)


@app.route('/about')
def about():
    """关于页面：渲染关于网站的信息"""
    # 渲染关于页面模板（模板路径：templates/pages/about.html）
    return render_template('pages/about.html')


# ==============================================================================
# 7. API接口路由：提供JSON格式的数据交互（面向前端/其他服务调用）
# ==============================================================================
@app.route('/movies', methods=['POST'])
def api_add_movie():
    """API接口：添加新电影（仅支持POST请求）"""
    # 获取前端传递的JSON数据
    data = request.json
    # 校验必填字段（title、year、genre、rating、poster_url）
    required_fields = ['title', 'year', 'genre', 'rating', 'poster_url']
    if not all(k in data for k in required_fields):
        # 缺少字段返回400错误（Bad Request）
        return jsonify({"status": "error", "msg": "缺少必填字段"}), 400

    # 创建电影实例（description字段可选，默认值为"暂无介绍"）
    movie = Movie(
        title=data['title'],
        year=data['year'],
        genre=data['genre'],
        rating=data['rating'],
        poster_url=data['poster_url'],
        description=data.get('description', '暂无介绍'),
        created_at=datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
        updated_at=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    )
    # 提交到数据库
    db.session.add(movie)
    db.session.commit()
    # 返回成功响应（包含新电影的ID）
    return jsonify({"status": "success", "id": movie.id})


@app.route('/users', methods=['POST'])
def api_add_user():
    """API接口：添加新用户（仅支持POST请求）"""
    # 获取前端传递的JSON数据
    data = request.json
    # 校验必填字段（username、email、password）
    required_fields = ['username', 'email', 'password']
    if not all(field in data for field in required_fields):
        return jsonify({"status": "error", "msg": "缺少必填字段"}), 400

    # 密码哈希处理（简单sha256加密，生产环境建议用werkzeug.security.generate_password_hash）
    password_hash = f"hash_{sha256(data['password'].encode()).hexdigest()}"

    # 创建用户实例
    user = User(
        username=data['username'],
        email=data['email'],
        password_hash=password_hash,
        created_at=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    )

    try:
        # 提交到数据库（捕获唯一性冲突等异常）
        db.session.add(user)
        db.session.commit()
        # 成功返回201状态码（Created）
        return jsonify({
            "status": "success",
            "msg": "用户创建成功",
            "user_id": user.id
        }), 201
    except Exception as e:
        # 异常时回滚会话，避免数据不一致
        db.session.rollback()
        # 返回错误信息（包含异常详情）
        return jsonify({"status": "error", "msg": f"创建失败：{str(e)}"}), 400


# ==============================================================================
# 8. 应用入口：本地开发时运行应用
# ==============================================================================
if __name__ == '__main__':
    # 启动Flask开发服务器（debug=True：开启调试模式，代码修改后自动重启）
    app.run(debug=True)
